package munci.core;

import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.LinkedList;

import munci.old.use.Tokens;

/**
 * A stack that maintains a nominal class type for each element,
 * including null elements
 */
public class Stack {

	/**
	 * stack of interleaved class and object
	 */
	private final LinkedList<Object> stack = new LinkedList<Object>();

	public void push(Object obj, Class<?> c) {
		c = type(obj, c);
		stack.addFirst(obj);
		stack.addFirst(c);
	}

	public void pushUnder(Object obj, Class<?> c) {
		stack.addLast(type(obj, c));
		stack.addLast(obj);
	}
	
	private static Class<?> type(Object obj, Class<?> c) {
		if (obj == null) return c;
		Class<?> exact = obj.getClass();
		return c.isAssignableFrom(exact)? exact : c;
	}
	
	public Object pop() {
		stack.removeFirst();
		return stack.removeFirst();
	}
	
	public Object popUnder() {
		Object result = stack.removeLast();
		stack.removeLast();
		return result;
	}
	
	public Object remove(int depth) {
		stack.remove(depth * 2);
		return stack.remove(depth * 2);
	}
	
	public Object peek() {
		return stack.get(1);
	}
	
	public void dup() {
		stack.addAll(0, stack.subList(0, 2));
	}

	public void swap() {
		stack.add(3, stack.remove());
		stack.add(3, stack.remove());
	}

	public void over(int depth) {
		stack.add(1 + 2 * depth, stack.remove());
		stack.add(1 + 2 * depth, stack.remove());
	}
	
	public int depth() {
		return stack.size() / 2;
	}
	
	public Object[] values() {
		Object[] ar = new Object[depth()];
		int x = 0, i = depth() - 1;
		for (Object obj : stack) {
			if (x++ % 2 == 1) {
				ar[i--] = obj;
			}
		}
		return ar;
	}

	public Class<?> peekClass(int depth) {
		return (Class<?>) stack.get(depth * 2);
	}
	
	public void print(PrintStream stream) {
		for (int i = 0; i < stack.size(); i += 2) {
			String type = ((Class<?>) stack.get(i)).getName();
			stream.print(type.substring(type.lastIndexOf('.') + 1).replace('$', '.'));
			stream.print(":");
			stream.println(String.valueOf(stack.get(i + 1)));
		}
	} 
	
	public final Word.R1<Boolean, Word> canInvoke = new WordImpl.R1<Boolean, Word>() {
		public Boolean call(Word word) {
			Class<?>[] params = word.parameterTypes();
			if (depth() < params.length) return false;

			for (int i = 0; i < params.length; i++) {
				if (!params[params.length - 1 - i].isAssignableFrom(peekClass(i))) {
					return false;
				}
			}
			return true;
		}
	};
	
	public void invoke(Word word) {
		Object[] args = new Object[word.parameterTypes().length];
		for (int i = args.length - 1; i >= 0; i--) {
			args[i] = pop();
		}
		Object result = word.invoke(args);
		Class<?> returnType = word.returnType();
		if (returnType != void.class) {
			push(result, returnType);
		}
	}
	
	public final Word.R1<Boolean, Word> canCompile = new WordImpl.R1<Boolean, Word>() {
		public Boolean call(Word word) {
			for (int i = 0, l = word.parameterTypes().length; i < l; i++) {
				if (!Tokens.class.isAssignableFrom(peekClass(i)))
					return false;
			}
			return true;
		}
	};

	public void compile(Field reference) {
		Tokens toks = new Tokens();
		toks.lex(reference.getClass().getCanonicalName());
		toks.add(".").add(reference.getName()).add("(");
		
		try {
			for (int i = ((Word) reference.get(null)).parameterTypes().length; i > 0; i--) {
				toks.addAll((Tokens) remove(i));
				if (i > 1) toks.add(",");
			}
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		Object obj = toks.add(")");
		push(obj, type(obj, Tokens.class));
	}
	
	@Override public String toString() {
		return Arrays.asList(values()).toString();
	}
}
